package com.util;

import com.file.FieldLoad;
import com.model.ClassInfo;
import com.model.attributes.*;
import com.model.field.FieldsInfo;
import com.model.method.MethodsInfo;
import com.model.pool.*;
import com.model.structure.*;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @author seven
 */
@Data
public class Analysis {
    /**
     * 字节码数组
     */
    private String[] byteCode;
    /**
     * 文件读取类
     */
    public final FieldLoad fieldLoad = new FieldLoad();
    /**
     * 字符串,字节数组工具类
     */
    public final StringByteUtil util = new StringByteUtil();
    /**
     * 数组读取下标
     */
    private int byteIndex = 0;

    /**
     * 构造函数
     *
     * @param path
     */
    public Analysis(String path) {
        byte[] bytes = fieldLoad.getFileDataByte(path);
        byteCode = util.byteToStr(bytes);
    }


    /**
     * 解析字节码内容为ClassInfo对象
     *
     * @return
     */
    public ClassInfo info() {
        ClassInfo info = new ClassInfo();
        //读取魔数
        readMagic(info);
        //读取次版本号
        readMinorVersion(info);
        //读取主版本号
        readMajorVersion(info);
        //读取常量池数量
        readConstantPoolCount(info);
        //读取常量池数据
        readConstantPool(info);
        //读取类访问权限
        readAccessFlags(info);
        //读取类名
        info.setThisClass(Integer.valueOf(readLengthValue(2)));
        //读取父类名
        info.setSuperClass(Integer.valueOf(readLengthValue(2)));
        //读取接口数量
        readInterfaceCount(info);
        //读取接口信息
        readInterface(info);
        //读取字段数量
        readFieldsCount(info);
        //读取字段信息
        readFieldInfo(info);
        //读取方法数量
        readMethodCount(info);
        //读取方法信息
        readMethod(info);
        //读取熟悉数量
        readAttributesCount(info);
        //读取熟悉列表
        readAttributesList(info);
        return info;
    }

    /**
     * 读取magic
     *
     * @param info
     */
    private void readMagic(ClassInfo info) {
        Magic magic = new Magic();
        magic.setValue(readValue(magic.getLen()));
        info.setMagic(magic);
    }

    /**
     * 读取主版本号
     *
     * @param info
     */
    private void readMajorVersion(ClassInfo info) {
        MajorVersion version = new MajorVersion();
        String value = util.sixteenToTenString(readValue(version.getLen()));
        version.setValue(value);
        info.setMajorVersion(version);
    }

    /**
     * 读取次版本号
     *
     * @param info
     */
    private void readMinorVersion(ClassInfo info) {
        MinorVersion version = new MinorVersion();
        String value = util.sixteenToTenString(readValue(version.getLen()));
        version.setValue(value);
        info.setMinorVersion(version);
    }

    /**
     * 读取常量池数量
     *
     * @param info
     */
    private void readConstantPoolCount(ClassInfo info) {
        ConstantPoolCount constantPoolCount = new ConstantPoolCount();
        Integer value = util.sixteenToTenInt(readValue(constantPoolCount.getLen()));
        constantPoolCount.setValue(value);
        info.setPoolCount(constantPoolCount);
    }

    /**
     * 读取常量池信息
     *
     * @param info
     */
    private void readConstantPool(ClassInfo info) {
        int count = info.getPoolCount().getValue();
        BaseConstantDecorate baseConstantDecorate = new BaseConstantDecorate();
        for (int i = 0; i < count - 1; i++) {
            baseConstantDecorate.invoke(info,this);
            if (info.getPoolList().get(info.getPoolList().size() - 1) instanceof ConstantLongInfo || info.getPoolList()
                .get(info.getPoolList().size() - 1) instanceof ConstantDoubleInfo) {
                info.getPoolList().add(new BaseConstantDecorate());
                i++;
            }
        }
    }

    /**
     * 读取类访问控制权限
     *
     * @param info
     */
    private void readAccessFlags(ClassInfo info) {
        AccessFlags flags = new AccessFlags();
        flags.setAccessFlags(readValue(flags.getLen()));
        info.setAccessFlags(flags);
    }

    /**
     * 读取指定长度value
     *
     * @param len
     */
    private String readLengthValue(int len) {
        return util.sixteenToTenString(readValue(len));
    }

    /**
     * 读取接口数量
     *
     * @param info
     */
    private void readInterfaceCount(ClassInfo info) {
        InterfaceCount count = new InterfaceCount();
        count.setValue(util.sixteenToTenInt(readValue(count.getLen())));
        info.setFaceCount(count);
    }

    /**
     * 读取接口信息
     *
     * @param info
     */
    private void readInterface(ClassInfo info) {
        List<InterfaceInfo> list = new ArrayList<InterfaceInfo>();
        if (info.getFaceCount().getValue().intValue() <= 0) {
            info.setFaceList(list);
            return;
        }
        for (int i = 0; i < info.getFaceCount().getValue(); i++) {
            InterfaceInfo face = new InterfaceInfo();
            face.setValue(util.sixteenToTenInt(readValue(face.getLen())));
            list.add(face);
        }
        info.setFaceList(list);
    }

    /**
     * 读取字段数量
     *
     * @param info
     */
    private void readFieldsCount(ClassInfo info) {
        FieldsCount fieldsCount = new FieldsCount();
        fieldsCount.setValue(util.sixteenToTenInt(readValue(fieldsCount.getLen())));
        info.setFieldsCount(fieldsCount);
    }

    /**
     * 读取字段信息
     *
     * @param info
     */
    private void readFieldInfo(ClassInfo info) {
        List<FieldsInfo> list = new ArrayList<FieldsInfo>();
        if (info.getFieldsCount().getValue().intValue() <= 0) {
            info.setFieldsList(list);
            return;
        }
        for (int i = 0; i < info.getFieldsCount().getValue(); i++) {
            FieldsInfo fields = new FieldsInfo();
            fields.setAccessFlag(readValue(fields.getFlagLen()));
            fields.setNameIndex(util.sixteenToTenInt(readValue(fields.getNameLen())));
            fields.setDescriptorIndex(util.sixteenToTenInt(readValue(fields.getDescriptorLen())));
            fields.setAttributesCount(util.sixteenToTenInt(readValue(fields.getAttributesLen())));
            List<AttributesInfo> attributesInfos = new ArrayList<AttributesInfo>();
            if (fields.getAttributesCount() <= 0) {
                fields.setAttributesList(attributesInfos);
                list.add(fields);
                continue;
            }
            for (int j = 0; j < fields.getAttributesCount(); j++) {
                readAttributes(info, attributesInfos);
            }
            fields.setAttributesList(attributesInfos);
            list.add(fields);
        }
        info.setFieldsList(list);
    }

    /**
     * 读取方法数量
     *
     * @param info
     */
    private void readMethodCount(ClassInfo info) {
        MethodsCount count = new MethodsCount();
        count.setValue(util.sixteenToTenInt(readValue(count.getLen())));
        info.setMethodsCount(count);
    }

    /**
     * 读取方法
     *
     * @param info
     */
    private void readMethod(ClassInfo info) {
        List<MethodsInfo> list = new ArrayList<MethodsInfo>();
        if (info.getMethodsCount().getValue().intValue() <= 0) {
            info.setMethodsList(list);
            return;
        }
        for (int i = 0; i < info.getMethodsCount().getValue(); i++) {
            MethodsInfo methodsInfo = new MethodsInfo();
            methodsInfo.setAccessFlag(readValue(methodsInfo.getFlagLen()));
            methodsInfo.setNameIndex(util.sixteenToTenInt(readValue(methodsInfo.getNameLen())));
            methodsInfo.setDescriptorIndex(util.sixteenToTenInt(readValue(methodsInfo.getDescriptorLen())));
            methodsInfo.setAttributesCount(util.sixteenToTenInt(readValue(methodsInfo.getAttributesLen())));
            List<AttributesInfo> attributesInfos = new ArrayList<AttributesInfo>();
            if (methodsInfo.getAttributesCount() <= 0) {
                methodsInfo.setAttributesList(attributesInfos);
                list.add(methodsInfo);
                continue;
            }
            for (int j = 0; j < methodsInfo.getAttributesCount(); j++) {
                readAttributes(info, attributesInfos);
            }
            methodsInfo.setAttributesList(attributesInfos);
            list.add(methodsInfo);
        }
        info.setMethodsList(list);
    }

    /**
     * 读取熟悉数量
     *
     * @param info
     */
    private void readAttributesCount(ClassInfo info) {
        AttributesCount count = new AttributesCount();
        count.setValue(util.sixteenToTenInt(readValue(count.getLen())));
        info.setAttributesCount(count);
    }

    /**
     * 读取熟悉列表
     *
     * @param info
     */
    private void readAttributesList(ClassInfo info) {
        List<AttributesInfo> infoList = new ArrayList<AttributesInfo>();
        if (info.getAttributesCount().getValue().intValue() <= 0) {
            info.setAttributesList(infoList);
        }
        for (int i = 0; i < info.getAttributesCount().getValue(); i++) {
            readAttributes(info, infoList);
        }
        info.setAttributesList(infoList);
    }


    /**
     * 读取单个属性
     *
     * @param info
     * @param attributesInfos
     */
    private void readAttributes(ClassInfo info, List<AttributesInfo> attributesInfos) {
        AttributesInfo attributesInfo = new AttributesInfo();
        attributesInfo.setNameIndex(util.sixteenToTenInt(readValue(attributesInfo.getNameLen())));
        attributesInfo.setAttributeLength(util.sixteenToTenInt(readValue(attributesInfo.getAttrLen())));
        ConstantUtfInfo utf8Info = (ConstantUtfInfo)info.getPoolList().get(attributesInfo.getNameIndex() - 1);
        String code = utf8Info.getBytes();
        putAttributes(code, attributesInfos, attributesInfo, info);
    }

    /**
     * 读取熟悉
     *
     * @param code
     * @param list
     * @param info
     * @param classInfo
     */
    private void putAttributes(String code, List<AttributesInfo> list, AttributesInfo info, ClassInfo classInfo) {
        switch (code) {
            case "Code":
                CodeAttributesInfo codes = new CodeAttributesInfo();
                codes.setAttributeNameIndex(info.getNameIndex());
                codes.setAttributeLength(info.getAttributeLength());
                codes.setMaxStack(util.sixteenToTenInt(readValue(codes.getMaxStackLen())));
                codes.setMaxLocals(util.sixteenToTenInt(readValue(codes.getMaxLocalsLen())));
                codes.setCodeLength(util.sixteenToTenInt(readValue(codes.getCodeLengthLen())));
                if (codes.getCodeLength().intValue() > 0) {
                    List<OpcodeAndOperand> operands = new ArrayList<OpcodeAndOperand>();
                    for (int i = 0; i < codes.getCodeLength().intValue(); i++) {
                        OpcodeAndOperand opcodeAndOperand = new OpcodeAndOperand();
                        String code_everyone_hexString = readValue(1);
                        String childCode = Instructions.codeConvertor(code_everyone_hexString.toLowerCase());
                        int j = OperandBytesJudge.operandBytesCount(childCode);
                        String operand = "";
                        int operand_int = 0;
                        //如果有操作数。 进行指针（行数）移动
                        if (j != 0) {
                            for (int k = 0; k < j; k++) {
                                i++;
                            }
                            operand = readValue(j);
                            operand_int = util.sixteenToTenInt(operand);
                        }
                        opcodeAndOperand.setOpcode(childCode);
                        if (childCode.equals("invokeinterface")) {
                            //存放之前的字符串形式。 用的时候再分割（ 0016 02 00），解析。
                            opcodeAndOperand.setOperand(operand);
                        } else {
                            opcodeAndOperand.setOperand(operand_int);
                        }
                        operands.add(opcodeAndOperand);
                    }
                    codes.setCodes(operands);
                }
                codes.setExceptionTableLength(util.sixteenToTenInt(readValue(codes.getExceptionTableLengthLen())));
                if (codes.getExceptionTableLength().intValue() > 0) {
                    List<Exceptions> exceptions = new ArrayList<Exceptions>();
                    for (int i = 0; i < codes.getExceptionTableLength().intValue(); i++) {
                        Exceptions e = new Exceptions();
                        e.setStartPc(util.sixteenToTenInt(readValue(e.getStartPcLen())));
                        e.setEndPc(util.sixteenToTenInt(readValue(e.getEndPcLen())));
                        e.setHandlerPc(util.sixteenToTenString(readValue(e.getHandlerPcLen())));
                        e.setCatchType(util.sixteenToTenString(readValue(e.getCatchTypeLen())));
                        exceptions.add(e);
                    }
                    codes.setExceptionTable(exceptions);
                }
                codes.setAttributesCount(util.sixteenToTenInt(readValue(codes.getAttributesCountLen())));
                if (codes.getAttributesCount().intValue() > 0) {
                    List<AttributesInfo> infoList = new ArrayList<AttributesInfo>();
                    for (int i = 0; i < codes.getAttributesCount(); i++) {
                        AttributesInfo attributesInfo = new AttributesInfo();
                        attributesInfo.setNameIndex(util.sixteenToTenInt(readValue(attributesInfo.getNameLen())));
                        attributesInfo.setAttributeLength(util.sixteenToTenInt(readValue(attributesInfo.getAttrLen())));
                        if (attributesInfo.getAttributeLength() > 0) {
                            ConstantUtfInfo utf8Info =
                                (ConstantUtfInfo)classInfo.getPoolList().get(attributesInfo.getNameIndex() - 1);
                            String attrCode = utf8Info.getBytes();
                            switch (attrCode) {
                                case "LineNumberTable":
                                    readLineNumberTable(attributesInfo, infoList);
                                    break;
                                case "LocalVariableTable":
                                    readLocalVariableTable(attributesInfo, infoList);
                                    break;
                                default:
                                    attributesInfo.setInfo(readValue(attributesInfo.getAttributeLength()));
                                    infoList.add(attributesInfo);
                                    break;
                            }
                        }
                    }
                    codes.setInfoList(infoList);
                }
                list.add(codes);
                break;
            case "Exceptions":
                AttributeExceptionsInfo exceptionsInfo = new AttributeExceptionsInfo();
                exceptionsInfo.setAttributeNameIndex(info.getNameIndex());
                exceptionsInfo.setAttributeLength(info.getAttributeLength());
                exceptionsInfo
                    .setNumberOfExceptions(util.sixteenToTenInt(readValue(exceptionsInfo.getNumberOfExceptionsLen())));
                if (exceptionsInfo.getNumberOfExceptions() <= 0) {
                    list.add(exceptionsInfo);
                    break;
                }
                List<Integer> exceptionIndexList = new ArrayList<Integer>();
                for (int i = 0; i < exceptionsInfo.getNumberOfExceptions(); i++) {
                    exceptionIndexList.add(util.sixteenToTenInt(readValue(exceptionsInfo.getNumberOfExceptionsLen())));
                }
                exceptionsInfo.setExceptionIndexTableList(exceptionIndexList);
                list.add(exceptionsInfo);
                break;
            case "LineNumberTable":
                readLineNumberTable(info, list);
                break;
            case "SourceFile":
                SourceFileInfo sourceFileInfo = new SourceFileInfo();
                sourceFileInfo.setAttributeNameIndex(info.getNameIndex());
                sourceFileInfo.setAttributeLength(info.getAttributeLength());
                sourceFileInfo
                    .setSourceFileIndex(util.sixteenToTenInt(readValue(sourceFileInfo.getSourceFileIndexLen())));
                list.add(sourceFileInfo);
                break;
            case "LocalVariableTable":
                readLocalVariableTable(info, list);
                break;
            case "ConstantValue":
                ConstantValue constantValue = new ConstantValue();
                constantValue.setAttributeNameIndex(info.getNameIndex());
                constantValue.setAttributeLength(info.getAttributeLength());
                constantValue
                    .setConstantValueIndex(util.sixteenToTenInt(readValue(constantValue.getConstantValueIndexLen())));
                list.add(constantValue);
                break;
            case "InnerClasses":
                AttributeInnerClassesInfo attributeInnerClassesInfo = new AttributeInnerClassesInfo();
                attributeInnerClassesInfo.setAttributeNameIndex(info.getNameIndex());
                attributeInnerClassesInfo.setAttributeLength(info.getAttributeLength());
                attributeInnerClassesInfo.setNumberOfClasses(
                    util.sixteenToTenInt(readValue(attributeInnerClassesInfo.getNumberOfClassesLen())));
                if (attributeInnerClassesInfo.getNumberOfClasses() <= 0) {
                    list.add(attributeInnerClassesInfo);
                    break;
                }
                List<InnerClassesInfo> innerClassesInfos = new ArrayList<InnerClassesInfo>();
                for (int i = 0; i < attributeInnerClassesInfo.getNumberOfClasses(); i++) {
                    InnerClassesInfo innerClassesInfo = new InnerClassesInfo();
                    innerClassesInfo.setInnerClassInfoIndex(
                        util.sixteenToTenInt(readValue(innerClassesInfo.getInnerClassInfoIndexLen())));
                    innerClassesInfo.setOuterClassInfoIndex(
                        util.sixteenToTenInt(readValue(innerClassesInfo.getOuterClassInfoIndexLen())));
                    innerClassesInfo
                        .setInnerNameIndex(util.sixteenToTenInt(readValue(innerClassesInfo.getInnerNameIndexLen())));
                    innerClassesInfo
                        .setInnerClassAccessFlags(readValue(innerClassesInfo.getInnerClassAccessFlagsLen()));
                    innerClassesInfos.add(innerClassesInfo);
                }
                attributeInnerClassesInfo.setInnerClassesList(innerClassesInfos);
                list.add(attributeInnerClassesInfo);
                break;
            case "Deprecated":
                AttributeDeprecatedInfo deprecatedInfo = new AttributeDeprecatedInfo();
                deprecatedInfo.setAttributeNameIndex(info.getNameIndex());
                deprecatedInfo.setAttributeLength(info.getAttributeLength());
                list.add(deprecatedInfo);
                break;
            case "Synthetic":
                SyntheticInfo syntheticInfo = new SyntheticInfo();
                syntheticInfo.setAttributeNameIndex(info.getNameIndex());
                syntheticInfo.setAttributeLength(info.getAttributeLength());
                list.add(syntheticInfo);
                break;
            case "Signature":
                SignatureInfo signatureInfo = new SignatureInfo();
                signatureInfo.setAttributeNameIndex(info.getNameIndex());
                signatureInfo.setAttributeLength(info.getAttributeLength());
                signatureInfo.setSignatureIndex(util.sixteenToTenInt(readValue(signatureInfo.getSignatureIndexLen())));
                list.add(signatureInfo);
                break;
            default:
                info.setInfo(readValue(info.getAttributeLength()));
                list.add(info);
                break;
        }
    }

    /**
     * 从数组中读取指定长度数据拼接为字符串
     *
     * @param len
     * @return
     */
    public String readValue(int len) {
        String v = util.copyOfRangeToJoin(byteCode, byteIndex, len);
        byteIndex += len;
        return v;
    }


    /**
     * 读取LineNumberTable
     *
     * @param attributesInfo
     * @param infoList
     */
    private void readLineNumberTable(AttributesInfo attributesInfo, List<AttributesInfo> infoList) {
        LineNumberTableInfo lineNumberTable = new LineNumberTableInfo();
        lineNumberTable.setAttributeNameIndex(attributesInfo.getNameIndex());
        lineNumberTable.setAttributeLength(attributesInfo.getAttributeLength());
        lineNumberTable
            .setLineNumberTableLength(util.sixteenToTenInt(readValue(lineNumberTable.getLineNumberTableLengthLen())));
        if (lineNumberTable.getLineNumberTableLength() <= 0) {
            infoList.add(lineNumberTable);
            return;
        }
        List<LineNumberInfo> lineNumberInfos = new ArrayList<LineNumberInfo>();
        for (int j = 0; j < lineNumberTable.getLineNumberTableLength(); j++) {
            LineNumberInfo numberInfo = new LineNumberInfo();
            numberInfo.setStartPc(util.sixteenToTenInt(readValue(numberInfo.getStartPcLen())));
            numberInfo.setLineNumber(util.sixteenToTenInt(readValue(numberInfo.getLineNumberLen())));
            lineNumberInfos.add(numberInfo);
        }
        lineNumberTable.setLineNumberTableList(lineNumberInfos);
        infoList.add(lineNumberTable);
    }

    /**
     * 读取LocalVariableTable
     *
     * @param attributesInfo
     * @param infoList
     */
    private void readLocalVariableTable(AttributesInfo attributesInfo, List<AttributesInfo> infoList) {
        LocalVariableTableInfo variableTableInfo = new LocalVariableTableInfo();
        variableTableInfo.setAttributeNameIndex(attributesInfo.getNameIndex());
        variableTableInfo.setAttributeLength(attributesInfo.getAttributeLength());
        variableTableInfo.setLocalVariableTableLength(
            util.sixteenToTenInt(readValue(variableTableInfo.getLocalVariableTableLengthLen())));
        if (variableTableInfo.getLocalVariableTableLength() <= 0) {
            infoList.add(variableTableInfo);
            return;
        }
        List<VariableInfo> variableInfos = new ArrayList<VariableInfo>();
        for (int j = 0; j < variableTableInfo.getLocalVariableTableLength(); j++) {
            VariableInfo variableInfo = new VariableInfo();
            variableInfo.setStartPc(util.sixteenToTenInt(readValue(variableInfo.getStartPcLen())));
            variableInfo.setLength(util.sixteenToTenInt(readValue(variableInfo.getLengthLen())));
            variableInfo.setNameIndex(util.sixteenToTenInt(readValue(variableInfo.getNameIndexLen())));
            variableInfo.setDescriptorIndex(util.sixteenToTenInt(readValue(variableInfo.getDescriptorIndexLen())));
            variableInfo.setIndex(util.sixteenToTenInt(readValue(variableInfo.getIndexLen())));
            variableInfos.add(variableInfo);
        }
        variableTableInfo.setLocalVariableTableList(variableInfos);
        infoList.add(variableTableInfo);
    }


}
